﻿using System;
using System.Collections;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media.Imaging;
using System.Windows.Media;
using DeepEarth.Core.Controls;
using DeepEarth.Core.Data;
namespace DeepEarth.Toolkit.Controls
{
    [TemplatePart(Name=PARTS_InsertAtTop, Type=typeof(Image))]
    [TemplatePart(Name=PART_DragHandle, Type=typeof(UIElement))]
    public class ReorderableLayerPanel : TreeView
    {
        const string PARTS_InsertAtTop = "PARTS_InsertAtTop";
        const string PART_DragHandle = "PART_DragHandle";

        public static readonly DependencyProperty TemplateSelectorProperty =
            DependencyProperty.Register("TemplateSelector", typeof(TemplateSelector), typeof(ReorderableLayerPanel), null);

        public TemplateSelector TemplateSelector
        {
            get { return (TemplateSelector)GetValue(TemplateSelectorProperty); }
            set { SetValue(TemplateSelectorProperty, value); }
        }

        private static void GeoItemsChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            if (args.NewValue != null)
            {
                ReorderableLayerPanel self = sender as ReorderableLayerPanel;
                GeoItems geoItems = args.NewValue as GeoItems;

                self.ItemsSource = geoItems.ViewModels;
            }
        }

        public GeoItems GeoItems
        {
            get { return (GeoItems)GetValue(GeoItemsProperty); }
            set { SetValue(GeoItemsProperty, value); }
        }

        // Using a DependencyProperty as the backing store for GeoItems.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty GeoItemsProperty =
            DependencyProperty.Register("GeoItems", typeof(GeoItems), typeof(ReorderableLayerPanel), new PropertyMetadata(GeoItemsChanged));


        public static readonly DependencyProperty LayerPanelItemSelectedCommandProperty =
            DependencyProperty.Register("LayerPanelItemSelectedCommand", typeof(ICommand), typeof(ReorderableLayerPanel), null);

        public ICommand LayerPanelItemSelectedCommand
        {
            get { return (ICommand)GetValue(LayerPanelItemSelectedCommandProperty); }
            set { SetValue(LayerPanelItemSelectedCommandProperty, value); }
        }

        Image insertAtTopImage;

        public ReorderableLayerPanel()
        {
            DefaultStyleKey = typeof(ReorderableLayerPanel);
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            insertAtTopImage = (Image)GetTemplateChild(PARTS_InsertAtTop);

            this.AddHandler(ReorderableLayerPanel.MouseLeftButtonUpEvent, new MouseButtonEventHandler((o, e) =>
            {
                this.OnMouseLeftButtonUp(e);

            }), true);
        }

        protected override bool IsItemItsOwnContainerOverride(object item)
        {
            return item is LayerPanelItem;
        }

        protected override DependencyObject GetContainerForItemOverride()
        {
            ReorderableLayerPanelItem lpi = new ReorderableLayerPanelItem();
            lpi.ItemTemplate = this.ItemTemplate;
            lpi.TemplateSelector = this.TemplateSelector;
            lpi.LayerPanelItemSelectedCommand = this.LayerPanelItemSelectedCommand;

            lpi.DragInitiatedHandler = (o,e) => this.OnMouseLeftButtonDown(e);

            StackPanel p = new StackPanel();
            p.Children.Add(lpi);

            Image i = new Image();
            i.Opacity = 0.65;

            p.Children.Add(i);

            return p;
        }

        protected override void PrepareContainerForItemOverride(System.Windows.DependencyObject element, object item)
        {
            GeoItemViewModel model = item as GeoItemViewModel;            
            StackPanel s = element as StackPanel;
            TreeViewItem tvi = s.Children[0] as TreeViewItem;

            if (this.TemplateSelector != null)
            {
                DataTemplate myTemplate = this.TemplateSelector.FindTemplate<DataTemplate>(model.Data);

                if (myTemplate == null)
                {
                    myTemplate = this.ItemTemplate;
                }

                if (tvi != null)
                {
                    tvi.HeaderTemplate = myTemplate;
                }
            }

            base.PrepareContainerForItemOverride(tvi, model.Data);
            LayerPanelItem.PrepareContainerForViewModel(tvi, model);
        }

        #region Reorder

        void MoveItem(int currentIndex, int targetIndex)
        {
            var list = this.ItemsSource as IList;
            if (list == null)
                throw new InvalidOperationException("ItemsSource must be an IList");
            var item = list[currentIndex];
            list.RemoveAt(currentIndex);

            if (currentIndex < targetIndex)
                list.Insert(targetIndex - 1, item);
            else
                list.Insert(targetIndex, item);
        }

        MouseButtonEventArgs mouseDownArgs;
        UIElement containerBeingDragged;

        protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
        {
            base.OnMouseLeftButtonDown(e);
            mouseDownArgs = e;
            e.Handled = false;
        }

        protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
        {
            base.OnMouseLeftButtonUp(e);
            mouseDownArgs = null;

            if (containerBeingDragged != null)
            {
                var targetIndex = FindDropIndex(e);
                var currentIndex = this.ItemContainerGenerator.IndexFromContainer(containerBeingDragged);
                if (currentIndex != targetIndex)
                    MoveItem(currentIndex, targetIndex);

                ReleaseMouseCapture();

                var itemBeingDragged = GetListViewItem(containerBeingDragged);
                itemBeingDragged.Visibility = Visibility.Visible;

                ClearDragImage();
                containerBeingDragged = null;
            }
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);

            if (mouseDownArgs != null && containerBeingDragged == null)
                StartDrag(mouseDownArgs);

            if (containerBeingDragged != null)
                SetDragImage(FindDropIndex(e));
        }

        void StartDrag(MouseEventArgs e)
        {
            containerBeingDragged = FindItemContainer(e);

            var itemBeingDragged = GetListViewItem(containerBeingDragged);
            if (itemBeingDragged != null)
            {
                itemBeingDragged.Visibility = Visibility.Collapsed;

                CaptureMouse();
            }
        }

        Image currentDragImage;
        void SetDragImage(int index)
        {
            Image newDragLocation;
            if (index == 0)
                newDragLocation = insertAtTopImage;
            else
            {
                var container = (UIElement)this.ItemContainerGenerator.ContainerFromIndex(index - 1);
                newDragLocation = GetSeparatorImage(container);
            }

            if (currentDragImage != newDragLocation)
            {
                if (currentDragImage == null)
                    newDragLocation.Source = new WriteableBitmap(GetListViewItem(containerBeingDragged), null);
                else
                {
                    newDragLocation.Source = currentDragImage.Source;
                    currentDragImage.Source = null;
                }
                currentDragImage = newDragLocation;
            }
        }

        void ClearDragImage()
        {
            if (currentDragImage != null)
            {
                currentDragImage.Source = null;
                currentDragImage = null;
            }
        }

        UIElement FindItemContainer(MouseEventArgs e)
        {
            return this.Items
                .Select(i => this.ItemContainerGenerator.ContainerFromItem(i))
                .Cast<FrameworkElement>()
                .FirstOrDefault(element => ElementContains(element, e.GetPosition(element)));
        }

        static bool ElementContains(FrameworkElement e, Point p)
        {
            return p.Y > 0 && p.Y < e.ActualHeight;
        }

        /// <summary>
        /// Finds which container we would drop into if released here
        /// </summary>
        int FindDropIndex(MouseEventArgs args)
        {
            if (currentDragImage != null)
                currentDragImage.Source = null; // Hide drag image briefly
            try
            {
                for (var i = 0; i < this.Items.Count; i++)
                {
                    var container = (FrameworkElement)this.ItemContainerGenerator.ContainerFromIndex(i);
                    var p = args.GetPosition(container);
                    if ((p.Y > 0 && p.Y < container.ActualHeight / 2) || p.Y < 0)
                    {
                        if (i == 0)
                            return 0;
                        return i;
                    }
                    if (p.Y > 0 && p.Y < container.ActualHeight)
                        return i + 1;
                }
            }
            finally
            {
                if (currentDragImage != null)
                    currentDragImage.Source = new WriteableBitmap(GetListViewItem(containerBeingDragged), null);
            }
            return this.Items.Count;
        }

        #region Template control lookup helpers
        static UIElement GetListViewItem(UIElement container)
        {
            if (container == null) return null;
            return VisualTreeHelper.GetChild(container, 0) as UIElement;
        }

        static Image GetSeparatorImage(UIElement container)
        {
            if (container == null) return null;
            return (Image)VisualTreeHelper.GetChild(container, 1);
        }
        #endregion

        #endregion
    }
}